﻿// ---------------------------------------------------------------------------------------------
// <copyright file="CycleDetectionManager.cs" company="Hiyoko">
//   Copyright (c) 2015 All Rights Reserved
// </copyright>
// <summary>
//   This class handles the circular dependencies detection.
// </summary>
// ---------------------------------------------------------------------------------------------

using Microsoft.Xrm.Sdk.Metadata;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Hiyoko.DependenciesSorter.CycleDetection
{
    public class CycleDetectionManager
    {
        #region Private members

        private List<Vertex<string>> _graph;

        #endregion Private members

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the CycleDetectionManager class.
        /// </summary>
        /// <param name="entityMetadatas">The entity metadatas</param>
        /// <param name="ignoredEntities">The list of entities to ignore</param>
        /// <param name="ignoredDependencies">The list of dependencies to ignore</param>
        public CycleDetectionManager(EntityMetadata[] entityMetadatas, List<string> ignoredEntities, Dictionary<string, List<string>> ignoredDependencies)
        {
            if (entityMetadatas.Length == 0)
            {
                throw new ArgumentException("An error occured: the argument <entityMetadatas> does not contain any element.");
            }

            if (ignoredEntities == null)
            {
                ignoredEntities = new List<string>();
            }

            if (ignoredDependencies == null)
            {
                ignoredDependencies = new Dictionary<string, List<string>>();
            }

            _graph = new List<Vertex<string>>();

            var vertexMetadatas = entityMetadatas.Where(em => !ignoredEntities.Contains(em.LogicalName))
                                                 .ToList();

            var vertexNames = vertexMetadatas.Select(em => em.LogicalName)
                                             .ToList();

            foreach (var name in vertexNames)
            {
                var vertex = new Vertex<string>(name);
                _graph.Add(vertex);
            }

            foreach (var entityMetadata in vertexMetadatas)
            {
                var vertex = _graph.Single(scc => scc.Value.Equals(entityMetadata.LogicalName));
                var dependencies = entityMetadata.GetManyToOneDependencies();
                foreach (var dependency in dependencies)
                {
                    if (!vertexNames.Contains(dependency)
                        || (ignoredDependencies.ContainsKey(vertex.Value) && ignoredDependencies[vertex.Value].Contains(dependency)))
                    {
                        continue;
                    }

                    var dependencyVertex = _graph.Single(scc => scc.Value.Equals(dependency));
                    vertex.Dependencies.Add(dependencyVertex);
                }
            }
        }

        #endregion Constructor

        #region Public methods

        /// <summary>
        /// Detect cycles in graph.
        /// </summary>
        public List<Cycle> DetectCycles()
        {
            var detector = new StronglyConnectedComponentFinder<string>();
            var components = detector.DetectCycle(_graph);

            var resultCycles = new List<Cycle>();
            if (components.Cycles().Any())
            {
                foreach (var cycle in components.Cycles())
                {
                    var resultCycle = new Cycle(cycle.GetList());
                    resultCycles.Add(resultCycle);
                }

                foreach (var resultCycle in resultCycles)
                {
                    resultCycle.CleanDependencies();
                }
            }
            return resultCycles;
        }

        #endregion Public methods
    }
}
